﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using ArcannEngine.Scene;
using ArcannEngine.Entities;
using ArcannEngine.Components;
using ArcannEngine.Graphics;

namespace ArcannEngine.Render
{
    public class RenderManager : Manager
    {
        // Podstawowy efekt do wyswietlania, w przyszlosci dorzuci sie nowe
        private BasicEffect effect;
        // Nazwa wykorzystywanej w danym momencie kamery, wspolna dla calego silnika - EngineCommon
        private String currentCameraName = "CAM";//EngineCommon.RootEntityName;
        // Grupa sprite'ow wyswietlanych podobnym sposobem
        private SpriteBatch spriteBatch;
        public DepthStencilState defaultDepthState = new DepthStencilState();
        SpriteFont font;

        Interface interface_;
        private Vector3 cameraPosition;
        private Vector3 cameraTarget;
        private Vector3 viewVector;
        private Matrix viewMatrix;
        private Matrix projectionMatrix;
        private BoundingFrustum frustrum;

        public RenderManager(RocketGame game)
            : base(game)
        {
            Initialize();
        }

        // Wczytanie domyslnego oswietlenia XNA
        private void LoadBasicEffect()
        {
            this.effect = new BasicEffect(this.GraphicsDevice);
            effect.EnableDefaultLighting();

            //effect.LightingEnabled = true; // turn on the lighting subsystem.
            //effect.DirectionalLight0.DiffuseColor = new Vector3(0.5f, 0, 0); // a red light
            //effect.DirectionalLight0.Direction = new Vector3(1, 0, 0);  // coming along the x-axis
            //effect.DirectionalLight0.SpecularColor = new Vector3(0, 1, 0);
        }

        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }

        // Dzieki temu menadzer mozna wyszukiwac w silniku po nazwie
        protected override string GetName()
        {
            return "Render";
        }

        protected override void Initialize()
        {
          
            

            //GraphicsDevice.DepthStencilState = defaultDepthState;
            //GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
            base.Initialize();
        }

        public GraphicsDeviceManager Graphics
        {
            get { return this.Game.Graphics; }
            set { this.Game.Graphics = value; }
        }

        public GraphicsDevice GraphicsDevice
        {
            get { return this.Game.GraphicsDevice; }
        }

        public override void LoadContent()
        {
            LoadBasicEffect();
            spriteBatch = new SpriteBatch(this.GraphicsDevice);
            font = Game.Content.Load<SpriteFont>("Czcionka");
            interface_ = new Interface(Game, (Game.GetManager("Scene") as SceneManager).rocket, spriteBatch, this.GraphicsDevice);
            defaultDepthState.DepthBufferEnable = true;
        }

        public override void Update(GameTime gameTime)
        {
            interface_.Update(gameTime);
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            // Pobranie z obiektu gry utworzonego menadzera sceny
            SceneManager sceneMgr = this.Game.GetManager("Scene") as SceneManager;
            if (null == sceneMgr)
            {
                throw new Exception("Scene manager not registered properly to the game engine");
            }

            // W silniku aktywna w danej chwili jest tylko jedna kamera
            // Odwolujemy sie do obiektu kamery poprzez nazwe pobrana z EngineCommon - currentCameraName
            Entity cameraEntity = sceneMgr.GetEntity(currentCameraName);
            if (null == cameraEntity)
            {
                throw new Exception("A camera entity must always exist if we are trying to render a scene");
            }

            // Mamy juz obiekt bedacy kamera, wiec teraz wczytujemy juz sam komponent kamery
            CameraComponent camComp = cameraEntity.GetComponent("Camera") as CameraComponent;
            if (null == camComp)
            {
                throw new Exception("An entity was designated as a camera but never given a camera component");
            }

            this.GraphicsDevice.Clear(Color.DarkSlateBlue);

            cameraPosition = (cameraEntity.GetComponent("Camera") as CameraComponent).camera_position;
            cameraTarget = (cameraEntity.GetComponent("Camera") as CameraComponent).target;
            viewVector = cameraPosition - cameraTarget;

            viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60.0f), camComp.AspectRatio, 100.0f, 10000f);


            //distortion.View = viewMatrix;
            //distortion.Projection = projectionMatrix;

            // uaktualnienie bryły widzenia
            frustrum = new BoundingFrustum(viewMatrix * projectionMatrix);

            // Zapisujemy do listy wszystkie obiekty w scenie
            Dictionary<string, Entity> entities = sceneMgr.Entities;

            // Tworzymy liste opisu renderingu
            List<RenderDescription> renderDescriptions = new List<RenderDescription>();

            // Kazdemu obiektowi przekazujemy liste opisu renderingu i jesli jest on wyposazony w komp. renderingu,
            // czyli ma reprezentacje w postaci modelu/prymitywu, to dopisuje informacje o sobie do listy
            foreach (KeyValuePair<string, Entity> pair in entities)
            {
                (pair.Value).Draw(gameTime, renderDescriptions);
            }

            // W zaleznosci od tego co zawiera obiekt, rysowany jest odpowiednio model albo prymityw
            foreach (RenderDescription desc in renderDescriptions)
            {
                // Model
                if (null != desc.model)
                {
                    // Copy any parent transforms.
                    Matrix[] transforms = new Matrix[desc.model.Bones.Count];
                    desc.model.CopyAbsoluteBoneTransformsTo(transforms);

                    // Draw the model. A model can have multiple meshes, so loop.
                    foreach (ModelMesh mesh in desc.model.Meshes)
                    {
                        Matrix meshMatrix;
                        Matrix.Multiply(ref transforms[mesh.ParentBone.Index], ref desc.worldTransform, out meshMatrix);
                        // This is where the mesh orientation is set, as well 
                        // as our camera and projection.
                        foreach (BasicEffect effect in mesh.Effects)
                        {
                            effect.World = meshMatrix;
                            effect.View = Matrix.CreateLookAt((cameraEntity.GetComponent("Camera") as CameraComponent).camera_position,
                                (cameraEntity.GetComponent("Camera") as CameraComponent).target, Vector3.Up);
                            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                                MathHelper.ToRadians(60.0f), camComp.AspectRatio, 100.0f, 5000000.0f);
                            effect.EnableDefaultLighting();
                            //effect.PreferPerPixelLighting = true;
                        }
                        // Draw the mesh, using the effects set above.
                        mesh.Draw();
                    }
                }
                // Prymityw
                else if (null != desc.geoPrim)
                {
                    // Set our vertex declaration, vertex buffer, and index buffer.
                    this.Game.GraphicsDevice.SetVertexBuffer(desc.geoPrim.VertexBuffer);
                    this.Game.GraphicsDevice.Indices = desc.geoPrim.IndexBuffer;

                    foreach (EffectPass effectPass in effect.CurrentTechnique.Passes)
                    {
                        effect.World = desc.worldTransform;
                        effect.View = Matrix.CreateLookAt(cameraEntity.position,
                                  (cameraEntity.position + cameraEntity.rotation.Forward), Vector3.Up);
                        effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                                  MathHelper.ToRadians(60.0f), camComp.AspectRatio, 1.0f, 20000.0f);

                        effectPass.Apply();

                        int primitiveCount = (desc.geoPrim.Indices.Count / 3);

                        this.Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                                     desc.geoPrim.Vertices.Count, 0, primitiveCount);

                    }
                }
            }
            this.interface_.Draw(gameTime);
            FixZBufferIssues();     
        }
        public void DrawInterfaceTemp()
        {
           /* spriteBatch.Begin();
            spriteBatch.DrawString(font, "siema", new Vector2(GraphicsDevice.Viewport.Width - 100, GraphicsDevice.Viewport.Height - 100), Color.White);
            spriteBatch.End();*/
            FixZBufferIssues();
        }

        public void FixZBufferIssues() //after spritebatch.end();
        {
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
        }
    }
}
